/**
 * @author Brian Alker
 * @author Edmundo Martinez
 *
 */

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketTimeoutException;
import java.util.Timer;
import java.util.TimerTask;

/**
 *	This class implements the destination specified in the project description.
 *	It periodically prints the number of packets received.
 */

class UDPDestination
{
   public int packetsReceived = 0;
   public int highSeq = 0;
   public String args[];
   public int port;
   public int ackPort;
   public boolean useARQ;
   public boolean writeFile;
   public File outFile;
   public FileOutputStream fos;
   public ByteArrayOutputStream bos = new ByteArrayOutputStream();
   
   public UDPDestination(String in_args[]) {
      args = in_args;
   }
   
   public void print(){
	   System.out.println("highestsequence: "+highSeq);
	   System.out.println("packetsReceived: "+packetsReceived);
   }
   /**
    * 
    * @throws Exception
    */
   public void run() throws Exception
   {
      port = Integer.parseInt(args[0]);
      ackPort = Integer.parseInt(args[1]);
      useARQ = Boolean.parseBoolean(args[2]);
      DatagramSocket serverSocket = new DatagramSocket(port);
      serverSocket.setSoTimeout(10000);
      DatagramSocket ackSocket = new DatagramSocket();
      byte[] receiveData = new byte[128];
      byte[] ackData = new byte[128];
      writeFile = (args.length == 5);
      
      Timer timer = new Timer();
      StatusPrinter statPrint = new StatusPrinter();
      timer.schedule(statPrint, 0, 2500);
      if(writeFile) {
         outFile = new File(args[4]);
         fos = new FileOutputStream(outFile);
      }
      
      while(true)
         {
            DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
            try {
               serverSocket.receive(receivePacket);
            } catch (SocketTimeoutException e){
               if(writeFile) {
                  byte bytes[] = bos.toByteArray();
                  fos.write(bytes);
                  fos.flush();
                  fos.close();   
                  timer.cancel();
                  return;
               } else {
                  continue;
               }
            }
            byte seqNumBytes[] = new byte[4];
            System.arraycopy(receivePacket.getData(), 8, seqNumBytes, 0, 4);
            int seqNum = UDPServer.byteArrayToInt(seqNumBytes);
            if(useARQ) {
               if(seqNum <= highSeq + 1) {
                  System.arraycopy(receivePacket.getData(), 0, ackData, 0, 128);
                  DatagramPacket ackPacket = new DatagramPacket(ackData, 128, InetAddress.getByName(args[3]), ackPort);
                  ackSocket.send(ackPacket);
               }
               
               if(seqNum == highSeq + 1) {
                  packetsReceived++;
                  highSeq++;               
                  byte readNumBytes[] = new byte[4];
                  System.arraycopy(receivePacket.getData(), 20, readNumBytes, 0, 4);
                  int readNum = UDPServer.byteArrayToInt(readNumBytes);
                  if(readNum != -1 && writeFile) {
                     bos.write(receivePacket.getData(), 24, readNum);
                  } else if(readNum == -1) {
                     if(writeFile) {
                        byte bytes[] = bos.toByteArray();
                        fos.write(bytes);
                        fos.flush();
                        fos.close();  
                     }
                     timer.cancel();
                     return;
                  }
               }
            } else {
               if(seqNum > highSeq) {
                  highSeq = seqNum;
               }
               packetsReceived++;
               byte readNumBytes[] = new byte[4];
               System.arraycopy(receivePacket.getData(), 20, readNumBytes, 0, 4);
               int readNum = UDPServer.byteArrayToInt(readNumBytes);
               if(readNum != -1 && writeFile) {
                  bos.write(receivePacket.getData(), 24, readNum);
               } else if(readNum == -1) {
                  if(writeFile) {
                     byte bytes[] = bos.toByteArray();
                     fos.write(bytes);
                     fos.flush();
                     fos.close();  
                  }
                  timer.cancel();
                  return;
               }
               if(seqNum > 10000){
                  break;
               }
            }
         }
   }
   
   /**
    * 
    * @param args arg0 is the port the dest listens on. arg1 is the port it sends an ack to. arg2 specifies whether or not to use ARQ. arg3 is the ip it sends an ack to. arg4 is the output filename
    * @throws Exception
    */
   public static void main(String args[]) throws Exception {
      UDPDestination ud = new UDPDestination(args);
      ud.run();
   }

   /**
    * Periodically prints out the number of packets received so far
    * @author Brian Alker
    *
    */
   private class StatusPrinter extends TimerTask {
         /**
          * Gets called every 2.5s to print out the number of packets received
          */
      public void run() {
            if(highSeq > 0) {
               System.out.println(port + ": " + packetsReceived + "/" + highSeq + " = " + ((float)packetsReceived)/highSeq);
            } else {
               System.out.println(port + ": " + packetsReceived);            
            }
      }
      
   }
}